P.6不能在编译时检查就在执行时检查

面向对象思考 2019-11-03 23:13:20

C++核心准则边译边学-P.6 不能在编译时检查就在执行时检查

P.6: What cannot be checked at compile time should be checkable at run time(不能在编译时检查的应当可以在执行时检查)

Reason(原因)

Leaving hard-to-detect errors in a program is asking for crashes and bad results.

在程序中留下难以检查的错误会直接导致程序崩溃和不良后果。

Note(注意)

Ideally, we catch all errors (that are not errors in the programmer's logic) at either compile time or run time. It is impossible to catch all errors at compile time and often not affordable to catch all remaining errors at run time. However, we should endeavor to write programs that in principle can be checked, given sufficient resources (analysis programs, run-time checks, machine resources, time).

理想情况下,我们会在编译时或执行时捕捉错误(不包括程序执行逻辑的错误)。我们不可能在编译时捕捉所有的错误,通常也无法做到在执行时检出剩下的所有错误。但即使是这样,我们还是应该努力按照可检查的原则写代码,就像有充足的资源(分析程序,执行检查,机器资源、时间)那样。

Example, bad(反面示例)

// separately compiled, possibly dynamically loaded
extern void f(int* p);
void g(int n)
{
    // bad: the number of elements is not passed to f()
    f(new int[n]);
}

Here, a crucial bit of information (the number of elements) has been so thoroughly "obscured" that static analysis is probably rendered infeasible and dynamic checking can be very difficult when f() is part of an ABI so that we cannot "instrument" that pointer. We could embed helpful information into the free store, but that requires global changes to a system and maybe to the compiler. What we have here is a design that makes error detection very hard.

代码中关于要素个数的重要的信息已经被彻底的忽略掉,因此当f()是ABI的一部分,而无法将指针转化为检查手段时,静态分析几乎不可能,动态检查也会非常困难。我们可以在自由存储中组入有用的信息,但是那需要对系统甚至编译器进行全局性的修改。我们面临的是一个让错误检出非常困难的设计。

译者注:ABI(Application Binary Interface)是操作系统为运行在该系统下的应用程序提供的二进制接口。。

Example, bad(反面示例)

We can of course pass the number of elements along with the pointer:

我们当然可以把要素的个数和指针一起传递过去:

// separately compiled, possibly dynamically loaded
extern void f2(int* p, int n);
void g2(int n)
{
    f2(new int[n], m); // bad: a wrong number of elements can be passed to f()
}

Passing the number of elements as an argument is better (and far more common) than just passing the pointer and relying on some (unstated) convention for knowing or discovering the number of elements. However (as shown), a simple typo can introduce a serious error. The connection between the two arguments of f2() is conventional, rather than explicit.

相比只是传递一个指针然后通过某种(心照不宣)的转换以获取元素个数的方式,将元素个数作为参数传递会比较好(也更常见)。但是正如代码中显示的,一个简单的打字错误就可能导入严重的错误。函数f2()的两个参数之间的关系可以说约定俗成,但还是没有达到明确的程度。

Also, it is implicit that f2() is supposed to delete its argument (or did the caller make a second mistake?).

另外,有一点比较含蓄,就是我们可以推断f2()会释放参数管理的内存(抑或这只是调用者的另一个错误?)

译者注:表达含糊也是错误的主要来源之一。

Example, bad(反面示例)

The standard library resource management pointers fail to pass the size when they point to an object:

当标准库中的资源管理指针指向一个对象时,如果传递大小信息会导致失败。

译者注:代码前的说明只是make_unique的补充说明,没有看出和本文的主题之间有什么关系。这段代码的问题应该在于同样的大小信息需要两次指定,这同样会带来出错的可能性。

Example(示例)

We need to pass the pointer and the number of elements as an integral object:

我们需要将指针和元素的个数作为一个整体传递:

extern void f4(vector&); // separately compiled, possibly dynamically loaded
extern void f4(span); // separately compiled, possibly dynamically loaded
 // NB: this assumes the calling code is ABI-compatible, using a
 // compatible C++ compiler and the same stdlib implementation
void g3(int n)
{ 
    vector v(n);
    f4(v); // pass a reference, retain ownership
    f4(span{v}); // pass a view, retain ownership
}

This design carries the number of elements along as an integral part of an object, so that errors are unlikely and dynamic (run-time) checking is always feasible, if not always affordable.

这个设计将元素的个数作为对象的一部分,因此不大可能引发错误而且动态(执行时)检查总是可行的,虽然这样做的代价可能十分巨大。

Example(示例)

How do we transfer both ownership and all information needed for validating use?

我们如何即传递所有权又传递用于检查的所有信息?

vector f5(int n) // OK: move
{
    vector v(n); 
    // ... initialize v ...
    return v;
}
unique_ptr f6(int n) // bad: loses n
{ 
    auto p = make_unique(n);
    // ... initialize *p ...
    return p;
}
owner f7(int n) // bad: loses n and we might forget to delete
{
    owner p = new int[n];
    // ... initialize *p ...
    return p;
}

译者注:第一段代码返回一个vector对象,它即包含指针又包含元素个数;第二段代码返回一个unique_ptr,所有权移交没有问题了,但是丢失了元素个数信息;第三段代码不但丢失了元素个数信息,而且没有办法保证内存一定会被删除。非常不幸,第三段代码正是我们日常开发中最常见的代码。

Example(示例)

??? T.B.D

show how possible checks are avoided by interfaces that pass polymorphic base classes around, when they actually know what they need? Or strings as "free-style" options希望表现到处传递多态基类指针的接口时多么容易逃避检查,使用者什么时候才能确切知道需要何种检查? 或者像“free-style"选项那样的文字描述也行。

译者注:作者虽然没有提供示例代码,但作者认为传递多态基类类型指针时无法进行有效检查这一点应该是确切无疑的。

Enforcement(实施建议)

Flag (pointer, count)-style interfaces (this will flag a lot of examples that can't be fixed for compatibility reasons)找出(指针,数量)-风格的接口(这可以找出很多由于兼容性理由而不能修改的例子)

??? T.B.D

译者注:由于兼容性的原因,发现有风险的接口很容易,但修改却很难。作为读者,至少争取在今后的代码中不要发生类似问题吧。

本页共92段,5178个字符,7652 Byte(字节)